#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum, IntEnum
from typing import List, Tuple, Dict

from pandas import DataFrame, Series

from core import threadLocal
from core.clock import Clock
from core.context import Context
from core.dataClasses import R, TacticResultData
from core.enums import Period, DateFormat, ThreadName
from core.exceptions import DataOverdueError
from core.index_base import IndexBase
from core.inventedtrader import TradeType, TradeInfo
from infrastructure.util import dateutils
from event.event_manager import Event, EventType, EventManager


class TacticType(Enum):
    """
    策略类型
    """
    common = 1
    delay = 2
    order = 3
    weight = 4
    count = 5


class Tactic:
    classInfo = "策略类"
    def __init__(self):
        self.context = Context.getInstance(threadLocal.get(ThreadName.CONTEXT.value))
        self.clock: Clock = Clock.getInstance(threadLocal.get(ThreadName.CLOCK.value))
        self.event_manager: EventManager = EventManager.getInstance(threadLocal.get(ThreadName.EVENT_MANAGER.value))
        self.period: Period = Period.day
        """
        分析周期
        """

    def setPeriod(self, period: Period = Period.day) -> Tactic:
        self.period = period
        self.context.setDefaultPeriod(period)
        return self

    def getPeriod(self):
        return self.context.period

    def emit(self, data: TacticResultData):
        self.event_manager.emit(Event(data.eventType, R.sucess(data)))

    def compute(self) -> bool:
        pass

'''

class Tactic:
    tacticType: TacticType = TacticType.common

    def __init__(self,
                 indexes: List[IndexBase],
                 trade_types: List[TradeType] = [TradeType.buy, TradeType.sell],
                 period: Period = Period.day,
                 support_stocks: List[str] = []
                 ):
        self.trade_types: List[TradeType] = trade_types
        """支持的下单类型"""
        self.buy_date: List[str] = None
        """买点日期"""
        self.sell_date: List[str] = None
        """卖点日期列表"""
        self.period: Period = period
        """策略支持的股票周期"""
        self.support_stocks: List[str] = support_stocks
        """支持的股票代码列表"""
        self.context: Context = None
        """股票历史数据"""
        self.event_manager: EventManager = EventManager.getInstance()
        """事件管理中心"""
        self.security: str = None
        self.indexes: List[IndexBase] = indexes
        """指标"""

    def set_trade_types(self, trade_types: List[TradeType]) -> Tactic:
        self.trade_types = trade_types
        return self

    def add_trade_type(self, trade_type: TradeType) -> Tactic:
        if self.trade_types == None:
            self.trade_types = []
        if trade_type is not None and trade_type not in self.trade_types:
            self.trade_types.append(trade_type)
        return self

    def set_period(self, period: Period) -> Tactic:
        self.period = period
        return self

    def set_support_stocks(self, support_stocks: List[str]) -> Tactic:
        self.support_stocks = support_stocks
        return self

    def set_event_manager(self, event_manager: EventManager) -> Tactic:
        self.event_manager = event_manager
        return self

    def add_stock(self, stock: str) -> Tactic:
        """增加支持的股票代码"""
        if stock != None and stock not in self.support_stocks:
            self.support_stocks.append(stock)
        return self

    def support(self, stock: str) -> bool:
        """判断给定的股票代码是否被当前策略所支持"""
        if self.support_stocks is None or len(self.support_stocks) == 0 or stock in self.support_stocks:
            return True
        return False

    def setup(self, context: Context) -> None:
        self.context = context
        self.security = context.security
        self.buy_date = []
        self.sell_date = []

    def __get_price(self, date_str: str) -> float:
        return self.context.get_close(date_str)

    def compute(self, date_str: str) -> TradeInfo:
        if not self.context.contain(date_str):
            raise DataOverdueError
        self.__compute_order_type(date_str)

    def __compute_trade_type(self, date_str: str) -> TradeInfo:
        trade_type = None
        if (self.trade_types is None or len(
                self.trade_types) == 0 or TradeType.buy in self.trade_types) and date_str in self.buy_date:
            trade_type = TradeType.buy
        elif (self.trade_types is None or len(
                self.trade_types) == 0 or TradeType.sell in self.trade_types) and date_str in self.sell_date:
            trade_type = TradeType.sell
        if trade_type:
            return TradeInfo().security(self.security).tradeType(trade_type).price(
                self.__get_price(date_str)).probability(1)


class Relation(Enum):
    """关系"""
    AND = 'and'
    OR = 'or'


class Appearance(IntEnum):
    """出现次数"""
    AT_LEAST_ONCE = 1
    AT_EVERY_TURN = 2


@dataclass
class SequenceTactic(Tactic):
    """
    序列策略
    这个策略只允许一种交易类型
    """

    class Condition:
        def __init__(self):
            pass

        def setup(self, context: Context) -> None:
            raise NotImplementedError()

        def compute(self, data: Series) -> bool:
            raise NotImplementedError()

    @dataclass
    class ConditionWrapper(Condition):
        description: str = ""
        """描述"""
        relation: Relation = None
        """pre_conditions之间的关系， and同时满足，or至少一个满足"""
        post_condition: any = None
        """后置条件"""
        pre_conditions: List[any] = None
        """前置条件"""
        is_postest: bool = False

        def add_pre_conditon(self, condition) -> SequenceTactic.ConditionWrapper:
            """增加前置条件"""
            if condition is None:
                return self
            self.pre_conditions.append(condition)
            if isinstance(condition, SequenceTactic.ConditionWrapper):
                condition.post_condition = self
            return self

        def set_post_condition(self, condition) -> SequenceTactic.ConditionWrapper:
            if condition is None:
                return self
            self.post_condition = condition
            return self

        def get_postest_condition_status(self):
            if isinstance(self.post_condition, SequenceTactic.ConditionItem):
                return self.post_condition.data is not None
            elif isinstance(self.post_condition, SequenceTactic.ConditionWrapper) and self.is_postest:
                return self.post_condition.get_postest_condition_status()

        def setup(self, context: Context) -> None:
            assert context is not None
            if self.pre_conditions is not None or len(self.pre_conditions) > 0:
                for con in self.pre_conditions:
                    con.setup(context)

        def compute(self, data: Series) -> bool:
            if data.isnull or self.key not in data:
                return False
            if self.pre_conditions is None or len(self.pre_conditions) == 0:
                return True
            if self.relation == Relation.AND:
                for con in self.pre_conditions:
                    if not con.compute(data):
                        return False
            elif self.relation == Relation.OR:
                for con in self.pre_conditions:
                    if con.compute(data):
                        return True
            return False

    @dataclass
    class ConditionItem(Condition):
        """
        序列条目
        """
        key: str
        index: IndexBase
        base_val: any
        appearance: Appearance
        """基准值"""
        data: Series
        """校验成功后这里非空"""

        def true_process(self, data: Series) -> bool:
            self.data = data
            return True

        def setup(self, context: Context) -> None:
            self.index.setup(context)

        def compute_item(self, data: Series) -> bool:
            raise NotImplementedError()

        def compute(self, data: Series) -> bool:
            if self.data is not None and self.appearance == Appearance.AT_LEAST_ONCE:
                return True
            return self.compute_item(data)

    @dataclass
    class FloatConditionItem(ConditionItem):
        """
        浮点数序列条目
        """
        base_val: float

    @dataclass
    class FloatConditionItemGT(FloatConditionItem):
        """浮点数大于操作序列条目"""

        def compute_item(self, data: Series) -> bool:
            if data.isnull or self.key not in data:
                return False
            if data[self.key] > self.base_val:
                return self.true_process(data)
            return False

    @dataclass
    class FloatConditionItemGE(FloatConditionItem):
        """浮点数大于等于操作序列条目"""

        def compute_item(self, data: Series) -> bool:
            if data.isnull or self.key not in data:
                return False
            if data[self.key] >= self.base_val:
                return self.true_process(data)
            return False

    @dataclass
    class FloatConditionItemLT(FloatConditionItem):
        """浮点数小于操作序列条目"""

        def compute_item(self, data: Series) -> bool:
            if data.isnull or self.key not in data:
                return False
            if data[self.key] < self.base_val:
                return self.true_process(data)
            return False

    @dataclass
    class FloatConditionItemLE(FloatConditionItem):
        """浮点数小于等于操作序列条目"""

        def compute_item(self, data: Series) -> bool:
            if data.isnull or self.key not in data:
                return False
            if data[self.key] <= self.base_val:
                return self.true_process(data)
            return False

    @dataclass
    class FloatConditionItemNE(FloatConditionItem):
        """浮点数不等于操作序列条目"""

        def compute_item(self, data: Series) -> bool:
            if data.isnull or self.key not in data:
                return False
            if data[self.key] != self.base_val:
                return self.true_process(data)
            return False

    @dataclass
    class BoolConditionItem(ConditionItem):
        """布尔值等于操作序列条目"""
        base_val: bool

        def compute_item(self, data: Series) -> bool:
            if data.isnull or self.key not in data:
                return False
            if data[self.key] == self.base_val:
                return self.true_process(data)
            return False

    def __init__(self, event_manager: EventManager, condition: Condition, indexes: List[IndexBase], trade_type: TradeType = TradeType.buy, period: Period = Period.day,
                 support_stocks: List[str] = []):
        super(SequenceTactic, self).__init__(event_manager, indexes, trade_types=[trade_type], period=period, support_stocks=support_stocks)
        self.condition: SequenceTactic.Condition = condition
        self.trade_types = [trade_type]

    def set_trade_types(self, tradeTypes: List[TradeType]) -> SequenceTactic:
        assert tradeTypes is not None and len(tradeTypes) == 1
        self.trade_types = tradeTypes
        return self

    def add_trade_type(self, trade_type: TradeType) -> SequenceTactic:
        """
        增加一个交易类型，因为序列策略只允许一个交易类型，所以这里会覆盖已存在的交易类型
        :param trade_type: 交易类型
        :return:
        """
        if self.trade_types == None:
            self.trade_types = []
        if trade_type is not None:
            self.trade_types = [trade_type]
        return self

    def get_unsucess_items(self) -> List[ConditionItem]:
        """
        返回一个没有成功的序列元素
        :return:
        """
        items: list = []
        for item in self.sequence:
            if item.data is not None:
                items.append(item)
        return items

    def setup(self, context: Context) -> None:
        assert context is not None
        assert self.condition is not None
        super(SequenceTactic, self).setup(context)
        self.condition.setup(context)

    def compute(self, date_str: str) -> TradeInfo:
        if self.condition is None:
            return None
        global_status = self.condition.compute(self.context[date_str])
        if self.condition.get_postest_condition_status():
            date: str = date_str
            date = dateutils.get_lastest_trading_dateStr_by_dateStr(
                dateutils.get_dateStr_before_num_days_by_dateStr(1, date, dateFormat=DateFormat.Y_m_d))
            while not global_status:
                row: Series = self.context.data[date]
                if row is None or row.isnull:
                    return None
                global_status = self.condition.compute(row)
            else:
                return TradeInfo().security(self.security).tradeType(self.trade_types[0])


class AbstractDelayTactic(Tactic):
    """
    延迟策略，当出现点位的时候延迟给定时间给出买卖点
    """
    tacticType: TacticType = TacticType.delay

    def __init__(self, event_manager: EventManager, indexes: List[IndexBase], delay: int = 1, trade_types: List[TradeType] = [TradeType.buy, TradeType.sell], period: Period = Period.day,
                 support_stocks: List[str] = []):
        super(AbstractDelayTactic, self).__init__(event_manager, indexes, trade_types=trade_types, period=period, support_stocks=support_stocks)
        self.delay: int = delay

    def delay(self, delay: int = 1) -> AbstractDelayTactic:
        """设置延迟周期"""
        self.delay = delay
        return self

    def compute(self, date_str: str) -> TradeInfo:
        if not self.context.contain(date_str):
            raise DataOverdueError
        date_str = dateutils.get_lastest_trading_dateStr_by_dateStr(
            dateStr=dateutils.get_dateStr_before_num_days_by_dateStr(self.delay, date_str,
                                                                     dateFormat=dateutils.DateFormat.Y_m_d),
            dateFormat=dateutils.DateFormat.Y_m_d)
        self.__compute_trade_type(date_str)


# class OrderTactic(Tactic):
#     """
#     排序策略
#     """
#     tacticType: TacticType = TacticType.order
#
#     def __init__(self, tactics: List[float]):
#         super(OrderTactic, self).__init__()
#         self.orderedTactics = tactics
#
#     def add(self, tactic: Tactic, order_num: int):
#         tactic.order_num = order_num
#         self.orderedTactics.append(tactic)
#         return self
#
#     def setup(self, context: Context):
#         super(OrderTactic, self).setup(context)
#         for tactic in self.orderedTactics:
#             tactic.setup(context)
#
#     def compute(self, date_str: str) -> (TradeType, float):
#         if not self.orderedTactics or len(self.orderedTactics) == 0:
#             return (None, 0)
#         if dateutils.get_timestamp_from_date(date_str, type="start") >= self.lastestdata_timestamp:
#             raise DataOverdueError
#         self.orderedTactics.sort(lambda item: item.order_num)
#         count = 0
#         trade_type = None
#         statistics = TacticStatistics(tactisType=self.__class__.tacticType)
#         for tactic in self.orderedTactics:
#             orderType_temp = tactic.compute(date_str)
#             if not trade_type:
#                 trade_type = orderType_temp
#                 count += 1
#             elif trade_type == orderType_temp:
#                 count += 1
#             else:
#                 return (trade_type, count / len(self.orderedTactics))
#         return (None, 0)


class WeightTactic(Tactic):
    """
    权重策略组合
    """
    tacticType: TacticType = TacticType.weight

    def __init__(self, event_manager: EventManager, indexes: List[IndexBase], probability_limit: float = 0.8, trade_types: List[TradeType] = [TradeType.buy, TradeType.sell], period: Period = Period.day,
                 support_stocks: List[str] = []):
        super(WeightTactic, self).__init__(event_manager, indexes, trade_types=trade_types, period=period, support_stocks=support_stocks)
        self.list = []
        self.is_root = False
        self.weight: int = 0
        self.probability_limit = probability_limit

    @staticmethod
    def start():
        tactic = WeightTactic()
        tactic.is_root = True
        return tactic

    def append(self, tactic: Tactic, weight: int):
        if not tactic:
            return None
        if isinstance(tactic, WeightTactic):
            return tactic
        weight = weight if weight > 0 else 1
        self.weight = self.weight + weight
        tactic.weight = weight
        self.list.append(tactic)
        return tactic

    def setup(self, context: Context):
        super(WeightTactic, self).setup(context)
        for tactic in self.list:
            tactic.setup(context)

    def compute(self, date_str: str) -> TradeInfo:
        decision_dict = self.__compute_weight(date_str)
        if decision_dict and len(decision_dict) > 0:
            result = self.__compute_trade_type(decision_dict)
            if result and len(result) > 0:
                probability = (result[1] * 1.0) / self.weight
                if probability >= self.probability_limit:
                    return TradeInfo().security(self.security()).tradeType(result[0]).price(
                        super(WeightTactic, self).__get_price(date_str)).probability(probability)
        return None

    def __compute_weight(self, date_str: str) -> Dict[str, int]:
        if len(self.follow) == 0:
            return None
        decision_dict = {}
        for tactic in self.list:
            if isinstance(tactic, WeightTactic):
                decision_dict_temp = tactic.__compute_weight(date_str)
                if decision_dict_temp and len(decision_dict_temp) > 0:
                    for k, v in TradeType.__members__.items():
                        decision_dict[k] = decision_dict.get(k, 0) + decision_dict_temp.get(k, 0)
            else:
                trade_info: TradeInfo = tactic.compute(date_str)
                if trade_info and (self.trade_types is None or len(
                        self.trade_types) == 0 or trade_info.tradeType in self.trade_types):
                    decision_dict[trade_info.tradeType.name] = decision_dict.get(trade_info.tradeType.name,
                                                                                 0) + tactic.weight
        else:
            return decision_dict

    def __compute_trade_type(self, decision_dict: dict) -> list:
        top = list()
        for key, val in decision_dict:
            if not top or len(top) == 0 or top[1] < val:
                top[0] = key
                top[1] = val
        return top


class CountTactic(WeightTactic):
    """
    数量策略
    """
    tacticType: TacticType = TacticType.count

    def __init__(self, event_manager: EventManager, indexes: List[IndexBase], probability_limit: float = 0.8, trade_types: List[TradeType] = [TradeType.buy, TradeType.sell], period: Period = Period.day,
                 support_stocks: List[str] = []):
        super(CountTactic, self).__init__(event_manager, indexes, probability_limit=probability_limit, trade_types=trade_types, period=period, support_stocks=support_stocks)

    def append(self, tactic: Tactic):
        super().append(tactic, weight=1)
'''
